package client

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// Client represents a DataFlare client
type Client struct {
	// baseURL is the base URL of the DataFlare API
	baseURL string
	// httpClient is the HTTP client
	httpClient *http.Client
	// apiKey is the API key
	apiKey string
	// timeout is the request timeout
	timeout time.Duration
}

// ClientOption represents a client option
type ClientOption func(*Client)

// WithTimeout sets the request timeout
func WithTimeout(timeout time.Duration) ClientOption {
	return func(c *Client) {
		c.timeout = timeout
	}
}

// WithAPIKey sets the API key
func WithAPIKey(apiKey string) ClientOption {
	return func(c *Client) {
		c.apiKey = apiKey
	}
}

// WithHTTPClient sets the HTTP client
func WithHTTPClient(httpClient *http.Client) ClientOption {
	return func(c *Client) {
		c.httpClient = httpClient
	}
}

// NewClient creates a new DataFlare client
func NewClient(baseURL string, options ...ClientOption) *Client {
	client := &Client{
		baseURL:    strings.TrimRight(baseURL, "/"),
		httpClient: &http.Client{},
		timeout:    30 * time.Second,
	}

	// Apply options
	for _, option := range options {
		option(client)
	}

	// Set timeout
	client.httpClient.Timeout = client.timeout

	return client
}

// Workflow represents a workflow
type Workflow struct {
	// ID is the workflow ID
	ID string `json:"id"`
	// Name is the workflow name
	Name string `json:"name"`
	// Description is the workflow description
	Description string `json:"description"`
	// Version is the workflow version
	Version string `json:"version"`
	// Status is the workflow status
	Status string `json:"status"`
	// CreatedAt is the creation time
	CreatedAt time.Time `json:"createdAt"`
	// UpdatedAt is the update time
	UpdatedAt time.Time `json:"updatedAt"`
}

// WorkflowRun represents a workflow run
type WorkflowRun struct {
	// ID is the run ID
	ID string `json:"id"`
	// WorkflowID is the workflow ID
	WorkflowID string `json:"workflowId"`
	// Status is the run status
	Status string `json:"status"`
	// StartTime is the start time
	StartTime time.Time `json:"startTime"`
	// EndTime is the end time
	EndTime time.Time `json:"endTime,omitempty"`
	// Error is the error message
	Error string `json:"error,omitempty"`
}

// CreateWorkflowRequest represents a create workflow request
type CreateWorkflowRequest struct {
	// Name is the workflow name
	Name string `json:"name"`
	// Description is the workflow description
	Description string `json:"description"`
	// Definition is the workflow definition
	Definition string `json:"definition"`
}

// UpdateWorkflowRequest represents an update workflow request
type UpdateWorkflowRequest struct {
	// Description is the workflow description
	Description string `json:"description,omitempty"`
	// Definition is the workflow definition
	Definition string `json:"definition,omitempty"`
}

// RunWorkflowRequest represents a run workflow request
type RunWorkflowRequest struct {
	// Parameters are the workflow parameters
	Parameters map[string]interface{} `json:"parameters,omitempty"`
}

// ListWorkflowsResponse represents a list workflows response
type ListWorkflowsResponse struct {
	// Workflows are the workflows
	Workflows []*Workflow `json:"workflows"`
	// NextToken is the next token for pagination
	NextToken string `json:"nextToken,omitempty"`
}

// ListWorkflowRunsResponse represents a list workflow runs response
type ListWorkflowRunsResponse struct {
	// Runs are the workflow runs
	Runs []*WorkflowRun `json:"runs"`
	// NextToken is the next token for pagination
	NextToken string `json:"nextToken,omitempty"`
}

// Error represents an API error
type Error struct {
	// Code is the error code
	Code string `json:"code"`
	// Message is the error message
	Message string `json:"message"`
}

// Error returns the error message
func (e *Error) Error() string {
	return fmt.Sprintf("%s: %s", e.Code, e.Message)
}

// CreateWorkflow creates a workflow
func (c *Client) CreateWorkflow(ctx context.Context, request *CreateWorkflowRequest) (*Workflow, error) {
	// Create request body
	body, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Create request
	req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/api/workflows", c.baseURL), strings.NewReader(string(body)))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusCreated {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var workflow Workflow
	err = json.NewDecoder(resp.Body).Decode(&workflow)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &workflow, nil
}

// GetWorkflow gets a workflow
func (c *Client) GetWorkflow(ctx context.Context, workflowID string) (*Workflow, error) {
	// Create request
	req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api/workflows/%s", c.baseURL, workflowID), nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusOK {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var workflow Workflow
	err = json.NewDecoder(resp.Body).Decode(&workflow)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &workflow, nil
}

// UpdateWorkflow updates a workflow
func (c *Client) UpdateWorkflow(ctx context.Context, workflowID string, request *UpdateWorkflowRequest) (*Workflow, error) {
	// Create request body
	body, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Create request
	req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/api/workflows/%s", c.baseURL, workflowID), strings.NewReader(string(body)))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusOK {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var workflow Workflow
	err = json.NewDecoder(resp.Body).Decode(&workflow)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &workflow, nil
}

// DeleteWorkflow deletes a workflow
func (c *Client) DeleteWorkflow(ctx context.Context, workflowID string) error {
	// Create request
	req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/api/workflows/%s", c.baseURL, workflowID), nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusNoContent {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return fmt.Errorf("failed to decode error response: %w", err)
		}
		return &apiError
	}

	return nil
}

// ListWorkflows lists workflows
func (c *Client) ListWorkflows(ctx context.Context, nextToken string, limit int) (*ListWorkflowsResponse, error) {
	// Create request
	url := fmt.Sprintf("%s/api/workflows", c.baseURL)
	if nextToken != "" || limit > 0 {
		url += "?"
		if nextToken != "" {
			url += fmt.Sprintf("nextToken=%s", nextToken)
		}
		if limit > 0 {
			if nextToken != "" {
				url += "&"
			}
			url += fmt.Sprintf("limit=%d", limit)
		}
	}
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusOK {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var response ListWorkflowsResponse
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &response, nil
}

// RunWorkflow runs a workflow
func (c *Client) RunWorkflow(ctx context.Context, workflowID string, request *RunWorkflowRequest) (*WorkflowRun, error) {
	// Create request body
	body, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Create request
	req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/api/workflows/%s/runs", c.baseURL, workflowID), strings.NewReader(string(body)))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusCreated {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var run WorkflowRun
	err = json.NewDecoder(resp.Body).Decode(&run)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &run, nil
}

// GetWorkflowRun gets a workflow run
func (c *Client) GetWorkflowRun(ctx context.Context, workflowID string, runID string) (*WorkflowRun, error) {
	// Create request
	req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api/workflows/%s/runs/%s", c.baseURL, workflowID, runID), nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusOK {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var run WorkflowRun
	err = json.NewDecoder(resp.Body).Decode(&run)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &run, nil
}

// ListWorkflowRuns lists workflow runs
func (c *Client) ListWorkflowRuns(ctx context.Context, workflowID string, nextToken string, limit int) (*ListWorkflowRunsResponse, error) {
	// Create request
	url := fmt.Sprintf("%s/api/workflows/%s/runs", c.baseURL, workflowID)
	if nextToken != "" || limit > 0 {
		url += "?"
		if nextToken != "" {
			url += fmt.Sprintf("nextToken=%s", nextToken)
		}
		if limit > 0 {
			if nextToken != "" {
				url += "&"
			}
			url += fmt.Sprintf("limit=%d", limit)
		}
	}
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusOK {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return nil, fmt.Errorf("failed to decode error response: %w", err)
		}
		return nil, &apiError
	}

	// Decode response
	var response ListWorkflowRunsResponse
	err = json.NewDecoder(resp.Body).Decode(&response)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &response, nil
}

// CancelWorkflowRun cancels a workflow run
func (c *Client) CancelWorkflowRun(ctx context.Context, workflowID string, runID string) error {
	// Create request
	req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/api/workflows/%s/runs/%s/cancel", c.baseURL, workflowID, runID), nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	// Set headers
	if c.apiKey != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
	}

	// Send request
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Check response
	if resp.StatusCode != http.StatusNoContent {
		var apiError Error
		err = json.NewDecoder(resp.Body).Decode(&apiError)
		if err != nil {
			return fmt.Errorf("failed to decode error response: %w", err)
		}
		return &apiError
	}

	return nil
}
